2015/03/04

Recent entries from same category

  1. Zenn で Twitter bot 作成入門を書いた。
  2. プログラマーのための新しい情報共有コミュニティ Zenn で本を書いてみた。
  3. Windows ユーザは cmd.exe で生きるべき 2020年版
  4. Let's Encrypt を簡単操作できる CLI、Lego が MyDNS に対応した。
  5. golang でメモ専用コマンド「memo」作った。

先日、Google が開発しているリモートプロシージャコール、gRPC を golang から使うチュートリアルを書きましたが

Big Sky :: Protocol Buffers を利用した RPC、gRPC を golang から試してみた。
http://mattn.kaoriya.net/software/lang/go/20150227144125.htm

今日は ruby と C++ から触ってみたいと思います。はじめに ruby の方ですが、Ruby 2.2.0 でビルドする事が出来ません。どうしても Ruby 2.2.0 から試したい人は、以下の PR にあるパッチを適用して下さい。

Support ruby 2.2.0 by mattn · Pull Request #894 - grpc/grpc - GitHub
https://github.com/grpc/grpc/pull/894

今回の検証は ruby 2.1.0 で行いました。まず proto ファイルから ruby のスタブを吐くには以下の様に実行します。

$ protoc --ruby_out=. --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_ROOT)/bins/opt/grpc_ruby_plugin customer_service.proto

GRPC_ROOT は grpc を checkout したディレクトリですが、システムにインストールした人は適便書き換えて下さい。golang の時の様にスタブが吐かれるのでサーバであれば

#!/usr/bin/env ruby

require 'grpc'
require 'customer_service_services'

class MyServer < Proto::CustomerService::Service
  def initialize()
    @customers = []
  end
  def add_person(arg, call)
    @customers << arg
    Proto::ResponseType.new
  end
  def list_person(args, call)
    @customers
  end
end

def main
  customers = []
  server = GRPC::RpcServer.new
  server.add_http2_port('0.0.0.0:11111')
  server.handle(MyServer.new)
  server.run
end

main

こんな感じ。クライアントであれば

#!/usr/bin/env ruby

require 'grpc'
require 'customer_service_services'

def main
  stub = Proto::CustomerService::Stub.new('localhost:11111')
  if ARGV.size == 2
    stub.add_person(Proto::Person.new(nameARGV[0], ageARGV[1].to_i))
  else
    stub.list_person(Proto::RequestType.new).each do |x|
      puts "name=#{x.name}, age=#{x.age}"
    end
  end
end

main

こんな感じに実装して下さい。今回は Sync サーバで実装しましたが、Async サーバで実装する場合は customers に排他を行うべきです。

C++ もやり方は変わりません。

protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_ROOT)/bins/opt/grpc_cpp_plugin customer_service.proto

protoc でスタブを吐いてサーバであれば

#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <grpc/grpc.h>
#include <grpc++/server.h>
#include <grpc++/server_builder.h>
#include <grpc++/server_context.h>
#include <grpc++/status.h>
#include <grpc++/stream.h>
#include "customer_service.pb.h"

using namespace proto;
using namespace grpc;

class CustomerServiceImpl final : public CustomerService::Service {
private:
  std::vector<Person> customers;
public:
  Status AddPerson(ServerContext* context, const Person* customer, ResponseType* response) {
    std::cout << "AddPerson" << std::endl;
    customers.push_back(*customer);
    std::cout << "Done" << std::endl;
    return Status::OK;
  }

  Status ListPerson(ServerContext* context, const RequestType* request, ServerWriter<Person>* writer) {
    std::cout << "ListPerson" << std::endl;
    for (const Person& p : customers) {
      writer->Write(p);
    }
    std::cout << "Done" << std::endl;
    return Status::OK;
  }
};

int
main(int argc, char* argv[]) {
  grpc_init();
  std::string server_address("0.0.0.0:11111");
  CustomerServiceImpl service;

  ServerBuilder builder;
  builder.AddPort(server_address);
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();
  grpc_shutdown();
  return 0;
}
またクライアントであれば #include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <grpc/grpc.h>
#include <grpc++/channel_arguments.h>
#include <grpc++/channel_interface.h>
#include <grpc++/client_context.h>
#include <grpc++/create_channel.h>
#include <grpc++/status.h>
#include <grpc++/stream.h>
#include "customer_service.pb.h"

using namespace proto;
using namespace grpc;

int
main(int argc, char** argv) {
  grpc_init();
  std::unique_ptr<CustomerService::Stub> client = CustomerService::NewStub(
      grpc::CreateChannelDeprecated("127.0.0.1:11111", ChannelArguments()));
  ClientContext context;
  RequestType request;
  ResponseType response;
  Person person;

  Status status;
  if (argc == 3) {
    person.set_name(argv[1]);
    person.set_age(atoi(argv[2]));
    status = client->AddPerson(&context, person, &response);
  } else {
    std::unique_ptr<ClientReader<Person>> reader = client->ListPerson(&context, request);
    while (reader->Read(&person)) {
      std::cout << "name=" << person.name() << ", age=" << person.age() << std::endl;
    }
    status = reader->Finish();
  }
  if (!status.IsOk()) {
    std::cout << "ListFeatures rpc failed." << std::endl;
  }
  client.reset();
  grpc_shutdown();
}

この様に実装します。同じ proto ファイルから生成したサーバやクライアントですので、ruby のサーバを起動して、golang のクライアントや C++ のクライアントからリクエストを送る事も出来ますし、C++ をサーバや golang をサーバにしても良いでしょう。

ただ触った感じですが、ruby のサーバは何故かレスポンスが悪かったので調査を兼ねてベンチマークを取ってみました。

シナリオは、ruby、C++、golang それぞれのサーバを起動して C++ のクライアントから要求します。計測は AddPerson の1000回呼び出し、とその1000件入った状態で ListPerson を100回呼び出しを計測しました。

ruby

mattn/grpc-example-rb - GitHub

AddPerson

$ time seq 1000 | xargs -n 1 ./client mattn

real    0m14.287s
user    0m6.667s
sys     0m4.607s

ListPerson

$ time seq 100 | xargs -n 1 ./client > /dev/null

real    0m12.385s
user    0m4.833s
sys     0m4.367s

cpp

mattn/grpc-example-cpp - GitHub

AddPerson

$ time seq 1000 | xargs -n 1 ./client mattn

real    0m12.326s
user    0m6.007s
sys     0m4.710s

ListPerson

$ time seq 100 | xargs -n 1 ./client > /dev/null

real    0m4.304s
user    0m2.430s
sys     0m1.490s

golang

mattn/grpc-example - GitHub

AddPerson

$ time seq 1000 | xargs -n 1 ./client mattn

real    0m13.067s
user    0m6.553s
sys     0m4.737s

ListPerson

$ time seq 100 | xargs -n 1 ./client > /dev/null

real    0m5.755s
user    0m2.767s
sys     0m1.983s

とまぁ、想定通りの結果が出ました。golang が案外頑張ってるなーという印象です。今後時間が出来たら Java や python も触っていく予定です。これまで3言語を同じ proto ファイルからスタブ生成して開発を行ってみましたが、各々扱い方が異なるのでちょっと混乱する可能性があります。自分の好きな言語で実装するのが良いと思います。

Posted at by